/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing;

import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.beans.*;

import java.util.Locale;
import java.util.Vector;
import java.util.Hashtable;
import javax.accessibility.*;
import javax.swing.plaf.PopupMenuUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.event.*;

import sun.awt.SunToolkit;
import sun.security.util.SecurityConstants;

import java.applet.Applet;

/**
 * An implementation of a popup menu -- a small window that pops up
 * and displays a series of choices. A <code>JPopupMenu</code> is used for the
 * menu that appears when the user selects an item on the menu bar.
 * It is also used for "pull-right" menu that appears when the
 * selects a menu item that activates it. Finally, a <code>JPopupMenu</code>
 * can also be used anywhere else you want a menu to appear.  For
 * example, when the user right-clicks in a specified area.
 * <p>
 * For information and examples of using popup menus, see
 * <a
 * href="https://docs.oracle.com/javase/tutorial/uiswing/components/menu.html">How to Use Menus</a>
 * in <em>The Java Tutorial.</em>
 * <p>
 * <strong>Warning:</strong> Swing is not thread safe. For more
 * information see <a
 * href="package-summary.html#threading">Swing's Threading
 * Policy</a>.
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans&trade;
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author Georges Saab
 * @author David Karlton
 * @author Arnaud Weber
 * @beaninfo attribute: isContainer false description: A small window that pops up and displays a
 * series of choices.
 */
@SuppressWarnings("serial")
public class JPopupMenu extends JComponent implements Accessible, MenuElement {

  /**
   * @see #getUIClassID
   * @see #readObject
   */
  private static final String uiClassID = "PopupMenuUI";

  /**
   * Key used in AppContext to determine if light way popups are the default.
   */
  private static final Object defaultLWPopupEnabledKey =
      new StringBuffer("JPopupMenu.defaultLWPopupEnabledKey");

  /**
   * Bug#4425878-Property javax.swing.adjustPopupLocationToFit introduced
   */
  static boolean popupPostionFixDisabled = false;

  static {
    popupPostionFixDisabled = java.security.AccessController.doPrivileged(
        new sun.security.action.GetPropertyAction(
            "javax.swing.adjustPopupLocationToFit", "")).equals("false");

  }

  transient Component invoker;
  transient Popup popup;
  transient Frame frame;
  private int desiredLocationX, desiredLocationY;

  private String label = null;
  private boolean paintBorder = true;
  private Insets margin = null;

  /**
   * Used to indicate if lightweight popups should be used.
   */
  private boolean lightWeightPopup = true;

  /*
   * Model for the selected subcontrol.
   */
  private SingleSelectionModel selectionModel;

  /* Lock object used in place of class object for synchronization.
   * (4187686)
   */
  private static final Object classLock = new Object();

  /* diagnostic aids -- should be false for production builds. */
  private static final boolean TRACE = false; // trace creates and disposes
  private static final boolean VERBOSE = false; // show reuse hits/misses
  private static final boolean DEBUG = false;  // show bad params, misc.

  /**
   * Sets the default value of the <code>lightWeightPopupEnabled</code>
   * property.
   *
   * @param aFlag <code>true</code> if popups can be lightweight, otherwise <code>false</code>
   * @see #getDefaultLightWeightPopupEnabled
   * @see #setLightWeightPopupEnabled
   */
  public static void setDefaultLightWeightPopupEnabled(boolean aFlag) {
    SwingUtilities.appContextPut(defaultLWPopupEnabledKey,
        Boolean.valueOf(aFlag));
  }

  /**
   * Gets the <code>defaultLightWeightPopupEnabled</code> property,
   * which by default is <code>true</code>.
   *
   * @return the value of the <code>defaultLightWeightPopupEnabled</code> property
   * @see #setDefaultLightWeightPopupEnabled
   */
  public static boolean getDefaultLightWeightPopupEnabled() {
    Boolean b = (Boolean)
        SwingUtilities.appContextGet(defaultLWPopupEnabledKey);
    if (b == null) {
      SwingUtilities.appContextPut(defaultLWPopupEnabledKey,
          Boolean.TRUE);
      return true;
    }
    return b.booleanValue();
  }

  /**
   * Constructs a <code>JPopupMenu</code> without an "invoker".
   */
  public JPopupMenu() {
    this(null);
  }

  /**
   * Constructs a <code>JPopupMenu</code> with the specified title.
   *
   * @param label the string that a UI may use to display as a title for the popup menu.
   */
  public JPopupMenu(String label) {
    this.label = label;
    lightWeightPopup = getDefaultLightWeightPopupEnabled();
    setSelectionModel(new DefaultSingleSelectionModel());
    enableEvents(AWTEvent.MOUSE_EVENT_MASK);
    setFocusTraversalKeysEnabled(false);
    updateUI();
  }


  /**
   * Returns the look and feel (L&amp;F) object that renders this component.
   *
   * @return the <code>PopupMenuUI</code> object that renders this component
   */
  public PopupMenuUI getUI() {
    return (PopupMenuUI) ui;
  }

  /**
   * Sets the L&amp;F object that renders this component.
   *
   * @param ui the new <code>PopupMenuUI</code> L&amp;F object
   * @beaninfo bound: true hidden: true attribute: visualUpdate true description: The UI object that
   * implements the Component's LookAndFeel.
   * @see UIDefaults#getUI
   */
  public void setUI(PopupMenuUI ui) {
    super.setUI(ui);
  }

  /**
   * Resets the UI property to a value from the current look and feel.
   *
   * @see JComponent#updateUI
   */
  public void updateUI() {
    setUI((PopupMenuUI) UIManager.getUI(this));
  }


  /**
   * Returns the name of the L&amp;F class that renders this component.
   *
   * @return the string "PopupMenuUI"
   * @see JComponent#getUIClassID
   * @see UIDefaults#getUI
   */
  public String getUIClassID() {
    return uiClassID;
  }

  protected void processFocusEvent(FocusEvent evt) {
    super.processFocusEvent(evt);
  }

  /**
   * Processes key stroke events such as mnemonics and accelerators.
   *
   * @param evt the key event to be processed
   */
  protected void processKeyEvent(KeyEvent evt) {
    MenuSelectionManager.defaultManager().processKeyEvent(evt);
    if (evt.isConsumed()) {
      return;
    }
    super.processKeyEvent(evt);
  }


  /**
   * Returns the model object that handles single selections.
   *
   * @return the <code>selectionModel</code> property
   * @see SingleSelectionModel
   */
  public SingleSelectionModel getSelectionModel() {
    return selectionModel;
  }

  /**
   * Sets the model object to handle single selections.
   *
   * @param model the new <code>SingleSelectionModel</code>
   * @beaninfo description: The selection model for the popup menu expert: true
   * @see SingleSelectionModel
   */
  public void setSelectionModel(SingleSelectionModel model) {
    selectionModel = model;
  }

  /**
   * Appends the specified menu item to the end of this menu.
   *
   * @param menuItem the <code>JMenuItem</code> to add
   * @return the <code>JMenuItem</code> added
   */
  public JMenuItem add(JMenuItem menuItem) {
    super.add(menuItem);
    return menuItem;
  }

  /**
   * Creates a new menu item with the specified text and appends
   * it to the end of this menu.
   *
   * @param s the string for the menu item to be added
   */
  public JMenuItem add(String s) {
    return add(new JMenuItem(s));
  }

  /**
   * Appends a new menu item to the end of the menu which
   * dispatches the specified <code>Action</code> object.
   *
   * @param a the <code>Action</code> to add to the menu
   * @return the new menu item
   * @see Action
   */
  public JMenuItem add(Action a) {
    JMenuItem mi = createActionComponent(a);
    mi.setAction(a);
    add(mi);
    return mi;
  }

  /**
   * Returns an point which has been adjusted to take into account of the
   * desktop bounds, taskbar and multi-monitor configuration.
   * <p>
   * This adustment may be cancelled by invoking the application with
   * -Djavax.swing.adjustPopupLocationToFit=false
   */
  Point adjustPopupLocationToFitScreen(int xPosition, int yPosition) {
    Point popupLocation = new Point(xPosition, yPosition);

    if (popupPostionFixDisabled == true || GraphicsEnvironment.isHeadless()) {
      return popupLocation;
    }

    // Get screen bounds
    Rectangle scrBounds;
    GraphicsConfiguration gc = getCurrentGraphicsConfiguration(popupLocation);
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    if (gc != null) {
      // If we have GraphicsConfiguration use it to get screen bounds
      scrBounds = gc.getBounds();
    } else {
      // If we don't have GraphicsConfiguration use primary screen
      scrBounds = new Rectangle(toolkit.getScreenSize());
    }

    // Calculate the screen size that popup should fit
    Dimension popupSize = JPopupMenu.this.getPreferredSize();
    long popupRightX = (long) popupLocation.x + (long) popupSize.width;
    long popupBottomY = (long) popupLocation.y + (long) popupSize.height;
    int scrWidth = scrBounds.width;
    int scrHeight = scrBounds.height;

    if (!canPopupOverlapTaskBar()) {
      // Insets include the task bar. Take them into account.
      Insets scrInsets = toolkit.getScreenInsets(gc);
      scrBounds.x += scrInsets.left;
      scrBounds.y += scrInsets.top;
      scrWidth -= scrInsets.left + scrInsets.right;
      scrHeight -= scrInsets.top + scrInsets.bottom;
    }
    int scrRightX = scrBounds.x + scrWidth;
    int scrBottomY = scrBounds.y + scrHeight;

    // Ensure that popup menu fits the screen
    if (popupRightX > (long) scrRightX) {
      popupLocation.x = scrRightX - popupSize.width;
    }

    if (popupBottomY > (long) scrBottomY) {
      popupLocation.y = scrBottomY - popupSize.height;
    }

    if (popupLocation.x < scrBounds.x) {
      popupLocation.x = scrBounds.x;
    }

    if (popupLocation.y < scrBounds.y) {
      popupLocation.y = scrBounds.y;
    }

    return popupLocation;
  }

  /**
   * Tries to find GraphicsConfiguration
   * that contains the mouse cursor position.
   * Can return null.
   */
  private GraphicsConfiguration getCurrentGraphicsConfiguration(
      Point popupLocation) {
    GraphicsConfiguration gc = null;
    GraphicsEnvironment ge =
        GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice[] gd = ge.getScreenDevices();
    for (int i = 0; i < gd.length; i++) {
      if (gd[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
        GraphicsConfiguration dgc =
            gd[i].getDefaultConfiguration();
        if (dgc.getBounds().contains(popupLocation)) {
          gc = dgc;
          break;
        }
      }
    }
    // If not found and we have invoker, ask invoker about his gc
    if (gc == null && getInvoker() != null) {
      gc = getInvoker().getGraphicsConfiguration();
    }
    return gc;
  }

  /**
   * Returns whether popup is allowed to be shown above the task bar.
   */
  static boolean canPopupOverlapTaskBar() {
    boolean result = true;

    Toolkit tk = Toolkit.getDefaultToolkit();
    if (tk instanceof SunToolkit) {
      result = ((SunToolkit) tk).canPopupOverlapTaskBar();
    }

    return result;
  }

  /**
   * Factory method which creates the <code>JMenuItem</code> for
   * <code>Actions</code> added to the <code>JPopupMenu</code>.
   *
   * @param a the <code>Action</code> for the menu item to be added
   * @return the new menu item
   * @see Action
   * @since 1.3
   */
  protected JMenuItem createActionComponent(Action a) {
    JMenuItem mi = new JMenuItem() {
      protected PropertyChangeListener createActionPropertyChangeListener(Action a) {
        PropertyChangeListener pcl = createActionChangeListener(this);
        if (pcl == null) {
          pcl = super.createActionPropertyChangeListener(a);
        }
        return pcl;
      }
    };
    mi.setHorizontalTextPosition(JButton.TRAILING);
    mi.setVerticalTextPosition(JButton.CENTER);
    return mi;
  }

  /**
   * Returns a properly configured <code>PropertyChangeListener</code>
   * which updates the control as changes to the <code>Action</code> occur.
   */
  protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
    return b.createActionPropertyChangeListener0(b.getAction());
  }

  /**
   * Removes the component at the specified index from this popup menu.
   *
   * @param pos the position of the item to be removed
   * @throws IllegalArgumentException if the value of <code>pos</code> &lt; 0, or if the value of
   * <code>pos</code> is greater than the number of items
   */
  public void remove(int pos) {
    if (pos < 0) {
      throw new IllegalArgumentException("index less than zero.");
    }
    if (pos > getComponentCount() - 1) {
      throw new IllegalArgumentException("index greater than the number of items.");
    }
    super.remove(pos);
  }

  /**
   * Sets the value of the <code>lightWeightPopupEnabled</code> property,
   * which by default is <code>true</code>.
   * By default, when a look and feel displays a popup,
   * it can choose to
   * use a lightweight (all-Java) popup.
   * Lightweight popup windows are more efficient than heavyweight
   * (native peer) windows,
   * but lightweight and heavyweight components do not mix well in a GUI.
   * If your application mixes lightweight and heavyweight components,
   * you should disable lightweight popups.
   * Some look and feels might always use heavyweight popups,
   * no matter what the value of this property.
   *
   * @param aFlag <code>false</code> to disable lightweight popups
   * @beaninfo description: Determines whether lightweight popups are used when possible expert:
   * true
   * @see #isLightWeightPopupEnabled
   */
  public void setLightWeightPopupEnabled(boolean aFlag) {
    // NOTE: this use to set the flag on a shared JPopupMenu, which meant
    // this effected ALL JPopupMenus.
    lightWeightPopup = aFlag;
  }

  /**
   * Gets the <code>lightWeightPopupEnabled</code> property.
   *
   * @return the value of the <code>lightWeightPopupEnabled</code> property
   * @see #setLightWeightPopupEnabled
   */
  public boolean isLightWeightPopupEnabled() {
    return lightWeightPopup;
  }

  /**
   * Returns the popup menu's label
   *
   * @return a string containing the popup menu's label
   * @see #setLabel
   */
  public String getLabel() {
    return label;
  }

  /**
   * Sets the popup menu's label.  Different look and feels may choose
   * to display or not display this.
   *
   * @param label a string specifying the label for the popup menu
   * @beaninfo description: The label for the popup menu. bound: true
   * @see #setLabel
   */
  public void setLabel(String label) {
    String oldValue = this.label;
    this.label = label;
    firePropertyChange("label", oldValue, label);
    if (accessibleContext != null) {
      accessibleContext.firePropertyChange(
          AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          oldValue, label);
    }
    invalidate();
    repaint();
  }

  /**
   * Appends a new separator at the end of the menu.
   */
  public void addSeparator() {
    add(new JPopupMenu.Separator());
  }

  /**
   * Inserts a menu item for the specified <code>Action</code> object at
   * a given position.
   *
   * @param a the <code>Action</code> object to insert
   * @param index specifies the position at which to insert the <code>Action</code>, where 0 is the
   * first
   * @throws IllegalArgumentException if <code>index</code> &lt; 0
   * @see Action
   */
  public void insert(Action a, int index) {
    JMenuItem mi = createActionComponent(a);
    mi.setAction(a);
    insert(mi, index);
  }

  /**
   * Inserts the specified component into the menu at a given
   * position.
   *
   * @param component the <code>Component</code> to insert
   * @param index specifies the position at which to insert the component, where 0 is the first
   * @throws IllegalArgumentException if <code>index</code> &lt; 0
   */
  public void insert(Component component, int index) {
    if (index < 0) {
      throw new IllegalArgumentException("index less than zero.");
    }

    int nitems = getComponentCount();
    // PENDING(ges): Why not use an array?
    Vector<Component> tempItems = new Vector<Component>();

        /* Remove the item at index, nitems-index times
           storing them in a temporary vector in the
           order they appear on the menu.
           */
    for (int i = index; i < nitems; i++) {
      tempItems.addElement(getComponent(index));
      remove(index);
    }

    add(component);

        /* Add the removed items back to the menu, they are
           already in the correct order in the temp vector.
           */
    for (Component tempItem : tempItems) {
      add(tempItem);
    }
  }

  /**
   * Adds a <code>PopupMenu</code> listener.
   *
   * @param l the <code>PopupMenuListener</code> to add
   */
  public void addPopupMenuListener(PopupMenuListener l) {
    listenerList.add(PopupMenuListener.class, l);
  }

  /**
   * Removes a <code>PopupMenu</code> listener.
   *
   * @param l the <code>PopupMenuListener</code> to remove
   */
  public void removePopupMenuListener(PopupMenuListener l) {
    listenerList.remove(PopupMenuListener.class, l);
  }

  /**
   * Returns an array of all the <code>PopupMenuListener</code>s added
   * to this JMenuItem with addPopupMenuListener().
   *
   * @return all of the <code>PopupMenuListener</code>s added or an empty array if no listeners have
   * been added
   * @since 1.4
   */
  public PopupMenuListener[] getPopupMenuListeners() {
    return listenerList.getListeners(PopupMenuListener.class);
  }

  /**
   * Adds a <code>MenuKeyListener</code> to the popup menu.
   *
   * @param l the <code>MenuKeyListener</code> to be added
   * @since 1.5
   */
  public void addMenuKeyListener(MenuKeyListener l) {
    listenerList.add(MenuKeyListener.class, l);
  }

  /**
   * Removes a <code>MenuKeyListener</code> from the popup menu.
   *
   * @param l the <code>MenuKeyListener</code> to be removed
   * @since 1.5
   */
  public void removeMenuKeyListener(MenuKeyListener l) {
    listenerList.remove(MenuKeyListener.class, l);
  }

  /**
   * Returns an array of all the <code>MenuKeyListener</code>s added
   * to this JPopupMenu with addMenuKeyListener().
   *
   * @return all of the <code>MenuKeyListener</code>s added or an empty array if no listeners have
   * been added
   * @since 1.5
   */
  public MenuKeyListener[] getMenuKeyListeners() {
    return listenerList.getListeners(MenuKeyListener.class);
  }

  /**
   * Notifies <code>PopupMenuListener</code>s that this popup menu will
   * become visible.
   */
  protected void firePopupMenuWillBecomeVisible() {
    Object[] listeners = listenerList.getListenerList();
    PopupMenuEvent e = null;
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == PopupMenuListener.class) {
        if (e == null) {
          e = new PopupMenuEvent(this);
        }
        ((PopupMenuListener) listeners[i + 1]).popupMenuWillBecomeVisible(e);
      }
    }
  }

  /**
   * Notifies <code>PopupMenuListener</code>s that this popup menu will
   * become invisible.
   */
  protected void firePopupMenuWillBecomeInvisible() {
    Object[] listeners = listenerList.getListenerList();
    PopupMenuEvent e = null;
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == PopupMenuListener.class) {
        if (e == null) {
          e = new PopupMenuEvent(this);
        }
        ((PopupMenuListener) listeners[i + 1]).popupMenuWillBecomeInvisible(e);
      }
    }
  }

  /**
   * Notifies <code>PopupMenuListeners</code> that this popup menu is
   * cancelled.
   */
  protected void firePopupMenuCanceled() {
    Object[] listeners = listenerList.getListenerList();
    PopupMenuEvent e = null;
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == PopupMenuListener.class) {
        if (e == null) {
          e = new PopupMenuEvent(this);
        }
        ((PopupMenuListener) listeners[i + 1]).popupMenuCanceled(e);
      }
    }
  }

  /**
   * Always returns true since popups, by definition, should always
   * be on top of all other windows.
   *
   * @return true
   */
  // package private
  boolean alwaysOnTop() {
    return true;
  }

  /**
   * Lays out the container so that it uses the minimum space
   * needed to display its contents.
   */
  public void pack() {
    if (popup != null) {
      Dimension pref = getPreferredSize();

      if (pref == null || pref.width != getWidth() ||
          pref.height != getHeight()) {
        showPopup();
      } else {
        validate();
      }
    }
  }

  /**
   * Sets the visibility of the popup menu.
   *
   * @param b true to make the popup visible, or false to hide it
   * @beaninfo bound: true description: Makes the popup visible
   */
  public void setVisible(boolean b) {
    if (DEBUG) {
      System.out.println("JPopupMenu.setVisible " + b);
    }

    // Is it a no-op?
    if (b == isVisible()) {
      return;
    }

    // if closing, first close all Submenus
    if (b == false) {

      // 4234793: This is a workaround because JPopupMenu.firePopupMenuCanceled is
      // a protected method and cannot be called from BasicPopupMenuUI directly
      // The real solution could be to make
      // firePopupMenuCanceled public and call it directly.
      Boolean doCanceled = (Boolean) getClientProperty("JPopupMenu.firePopupMenuCanceled");
      if (doCanceled != null && doCanceled == Boolean.TRUE) {
        putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.FALSE);
        firePopupMenuCanceled();
      }
      getSelectionModel().clearSelection();

    } else {
      // This is a popup menu with MenuElement children,
      // set selection path before popping up!
      if (isPopupMenu()) {
        MenuElement me[] = new MenuElement[1];
        me[0] = this;
        MenuSelectionManager.defaultManager().setSelectedPath(me);
      }
    }

    if (b) {
      firePopupMenuWillBecomeVisible();
      showPopup();
      firePropertyChange("visible", Boolean.FALSE, Boolean.TRUE);


    } else if (popup != null) {
      firePopupMenuWillBecomeInvisible();
      popup.hide();
      popup = null;
      firePropertyChange("visible", Boolean.TRUE, Boolean.FALSE);
      // 4694797: When popup menu is made invisible, selected path
      // should be cleared
      if (isPopupMenu()) {
        MenuSelectionManager.defaultManager().clearSelectedPath();
      }
    }
  }

  /**
   * Retrieves <code>Popup</code> instance from the
   * <code>PopupMenuUI</code> that has had <code>show</code> invoked on
   * it. If the current <code>popup</code> is non-null,
   * this will invoke <code>dispose</code> of it, and then
   * <code>show</code> the new one.
   * <p>
   * This does NOT fire any events, it is up the caller to dispatch
   * the necessary events.
   */
  private void showPopup() {
    Popup oldPopup = popup;

    if (oldPopup != null) {
      oldPopup.hide();
    }
    PopupFactory popupFactory = PopupFactory.getSharedInstance();

    if (isLightWeightPopupEnabled()) {
      popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP);
    } else {
      popupFactory.setPopupType(PopupFactory.HEAVY_WEIGHT_POPUP);
    }

    // adjust the location of the popup
    Point p = adjustPopupLocationToFitScreen(desiredLocationX, desiredLocationY);
    desiredLocationX = p.x;
    desiredLocationY = p.y;

    Popup newPopup = getUI().getPopup(this, desiredLocationX,
        desiredLocationY);

    popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP);
    popup = newPopup;
    newPopup.show();
  }

  /**
   * Returns true if the popup menu is visible (currently
   * being displayed).
   */
  public boolean isVisible() {
    return popup != null;
  }

  /**
   * Sets the location of the upper left corner of the
   * popup menu using x, y coordinates.
   * <p>
   * The method changes the geometry-related data. Therefore,
   * the native windowing system may ignore such requests, or it may modify
   * the requested data, so that the {@code JPopupMenu} object is placed and sized
   * in a way that corresponds closely to the desktop settings.
   *
   * @param x the x coordinate of the popup's new position in the screen's coordinate space
   * @param y the y coordinate of the popup's new position in the screen's coordinate space
   * @beaninfo description: The location of the popup menu.
   */
  public void setLocation(int x, int y) {
    int oldX = desiredLocationX;
    int oldY = desiredLocationY;

    desiredLocationX = x;
    desiredLocationY = y;
    if (popup != null && (x != oldX || y != oldY)) {
      showPopup();
    }
  }

  /**
   * Returns true if the popup menu is a standalone popup menu
   * rather than the submenu of a <code>JMenu</code>.
   *
   * @return true if this menu is a standalone popup menu, otherwise false
   */
  private boolean isPopupMenu() {
    return ((invoker != null) && !(invoker instanceof JMenu));
  }

  /**
   * Returns the component which is the 'invoker' of this
   * popup menu.
   *
   * @return the <code>Component</code> in which the popup menu is displayed
   */
  public Component getInvoker() {
    return this.invoker;
  }

  /**
   * Sets the invoker of this popup menu -- the component in which
   * the popup menu menu is to be displayed.
   *
   * @param invoker the <code>Component</code> in which the popup menu is displayed
   * @beaninfo description: The invoking component for the popup menu expert: true
   */
  public void setInvoker(Component invoker) {
    Component oldInvoker = this.invoker;
    this.invoker = invoker;
    if ((oldInvoker != this.invoker) && (ui != null)) {
      ui.uninstallUI(this);
      ui.installUI(this);
    }
    invalidate();
  }

  /**
   * Displays the popup menu at the position x,y in the coordinate
   * space of the component invoker.
   *
   * @param invoker the component in whose space the popup menu is to appear
   * @param x the x coordinate in invoker's coordinate space at which the popup menu is to be
   * displayed
   * @param y the y coordinate in invoker's coordinate space at which the popup menu is to be
   * displayed
   */
  public void show(Component invoker, int x, int y) {
    if (DEBUG) {
      System.out.println("in JPopupMenu.show ");
    }
    setInvoker(invoker);
    Frame newFrame = getFrame(invoker);
    if (newFrame != frame) {
      // Use the invoker's frame so that events
      // are propagated properly
      if (newFrame != null) {
        this.frame = newFrame;
        if (popup != null) {
          setVisible(false);
        }
      }
    }
    Point invokerOrigin;
    if (invoker != null) {
      invokerOrigin = invoker.getLocationOnScreen();

      // To avoid integer overflow
      long lx, ly;
      lx = ((long) invokerOrigin.x) +
          ((long) x);
      ly = ((long) invokerOrigin.y) +
          ((long) y);
      if (lx > Integer.MAX_VALUE) {
        lx = Integer.MAX_VALUE;
      }
      if (lx < Integer.MIN_VALUE) {
        lx = Integer.MIN_VALUE;
      }
      if (ly > Integer.MAX_VALUE) {
        ly = Integer.MAX_VALUE;
      }
      if (ly < Integer.MIN_VALUE) {
        ly = Integer.MIN_VALUE;
      }

      setLocation((int) lx, (int) ly);
    } else {
      setLocation(x, y);
    }
    setVisible(true);
  }

  /**
   * Returns the popup menu which is at the root of the menu system
   * for this popup menu.
   *
   * @return the topmost grandparent <code>JPopupMenu</code>
   */
  JPopupMenu getRootPopupMenu() {
    JPopupMenu mp = this;
    while ((mp != null) && (mp.isPopupMenu() != true) &&
        (mp.getInvoker() != null) &&
        (mp.getInvoker().getParent() != null) &&
        (mp.getInvoker().getParent() instanceof JPopupMenu)
        ) {
      mp = (JPopupMenu) mp.getInvoker().getParent();
    }
    return mp;
  }

  /**
   * Returns the component at the specified index.
   *
   * @param i the index of the component, where 0 is the first
   * @return the <code>Component</code> at that index
   * @deprecated replaced by {@link java.awt.Container#getComponent(int)}
   */
  @Deprecated
  public Component getComponentAtIndex(int i) {
    return getComponent(i);
  }

  /**
   * Returns the index of the specified component.
   *
   * @param c the <code>Component</code> to find
   * @return the index of the component, where 0 is the first; or -1 if the component is not found
   */
  public int getComponentIndex(Component c) {
    int ncomponents = this.getComponentCount();
    Component[] component = this.getComponents();
    for (int i = 0; i < ncomponents; i++) {
      Component comp = component[i];
      if (comp == c) {
        return i;
      }
    }
    return -1;
  }

  /**
   * Sets the size of the Popup window using a <code>Dimension</code> object.
   * This is equivalent to <code>setPreferredSize(d)</code>.
   *
   * @param d the <code>Dimension</code> specifying the new size of this component.
   * @beaninfo description: The size of the popup menu
   */
  public void setPopupSize(Dimension d) {
    Dimension oldSize = getPreferredSize();

    setPreferredSize(d);
    if (popup != null) {
      Dimension newSize = getPreferredSize();

      if (!oldSize.equals(newSize)) {
        showPopup();
      }
    }
  }

  /**
   * Sets the size of the Popup window to the specified width and
   * height. This is equivalent to
   * <code>setPreferredSize(new Dimension(width, height))</code>.
   *
   * @param width the new width of the Popup in pixels
   * @param height the new height of the Popup in pixels
   * @beaninfo description: The size of the popup menu
   */
  public void setPopupSize(int width, int height) {
    setPopupSize(new Dimension(width, height));
  }

  /**
   * Sets the currently selected component,  This will result
   * in a change to the selection model.
   *
   * @param sel the <code>Component</code> to select
   * @beaninfo description: The selected component on the popup menu expert: true hidden: true
   */
  public void setSelected(Component sel) {
    SingleSelectionModel model = getSelectionModel();
    int index = getComponentIndex(sel);
    model.setSelectedIndex(index);
  }

  /**
   * Checks whether the border should be painted.
   *
   * @return true if the border is painted, false otherwise
   * @see #setBorderPainted
   */
  public boolean isBorderPainted() {
    return paintBorder;
  }

  /**
   * Sets whether the border should be painted.
   *
   * @param b if true, the border is painted.
   * @beaninfo description: Is the border of the popup menu painted
   * @see #isBorderPainted
   */
  public void setBorderPainted(boolean b) {
    paintBorder = b;
    repaint();
  }

  /**
   * Paints the popup menu's border if the <code>borderPainted</code>
   * property is <code>true</code>.
   *
   * @param g the <code>Graphics</code> object
   * @see JComponent#paint
   * @see JComponent#setBorder
   */
  protected void paintBorder(Graphics g) {
    if (isBorderPainted()) {
      super.paintBorder(g);
    }
  }

  /**
   * Returns the margin, in pixels, between the popup menu's border and
   * its containers.
   *
   * @return an <code>Insets</code> object containing the margin values.
   */
  public Insets getMargin() {
    if (margin == null) {
      return new Insets(0, 0, 0, 0);
    } else {
      return margin;
    }
  }


  /**
   * Examines the list of menu items to determine whether
   * <code>popup</code> is a popup menu.
   *
   * @param popup a <code>JPopupMenu</code>
   * @return true if <code>popup</code>
   */
  boolean isSubPopupMenu(JPopupMenu popup) {
    int ncomponents = this.getComponentCount();
    Component[] component = this.getComponents();
    for (int i = 0; i < ncomponents; i++) {
      Component comp = component[i];
      if (comp instanceof JMenu) {
        JMenu menu = (JMenu) comp;
        JPopupMenu subPopup = menu.getPopupMenu();
        if (subPopup == popup) {
          return true;
        }
        if (subPopup.isSubPopupMenu(popup)) {
          return true;
        }
      }
    }
    return false;
  }


  private static Frame getFrame(Component c) {
    Component w = c;

    while (!(w instanceof Frame) && (w != null)) {
      w = w.getParent();
    }
    return (Frame) w;
  }


  /**
   * Returns a string representation of this <code>JPopupMenu</code>.
   * This method
   * is intended to be used only for debugging purposes, and the
   * content and format of the returned string may vary between
   * implementations. The returned string may be empty but may not
   * be <code>null</code>.
   *
   * @return a string representation of this <code>JPopupMenu</code>.
   */
  protected String paramString() {
    String labelString = (label != null ?
        label : "");
    String paintBorderString = (paintBorder ?
        "true" : "false");
    String marginString = (margin != null ?
        margin.toString() : "");
    String lightWeightPopupEnabledString = (isLightWeightPopupEnabled() ?
        "true" : "false");
    return super.paramString() +
        ",desiredLocationX=" + desiredLocationX +
        ",desiredLocationY=" + desiredLocationY +
        ",label=" + labelString +
        ",lightWeightPopupEnabled=" + lightWeightPopupEnabledString +
        ",margin=" + marginString +
        ",paintBorder=" + paintBorderString;
  }

/////////////////
// Accessibility support
////////////////

  /**
   * Gets the AccessibleContext associated with this JPopupMenu.
   * For JPopupMenus, the AccessibleContext takes the form of an
   * AccessibleJPopupMenu.
   * A new AccessibleJPopupMenu instance is created if necessary.
   *
   * @return an AccessibleJPopupMenu that serves as the AccessibleContext of this JPopupMenu
   */
  public AccessibleContext getAccessibleContext() {
    if (accessibleContext == null) {
      accessibleContext = new AccessibleJPopupMenu();
    }
    return accessibleContext;
  }

  /**
   * This class implements accessibility support for the
   * <code>JPopupMenu</code> class.  It provides an implementation of the
   * Java Accessibility API appropriate to popup menu user-interface
   * elements.
   */
  @SuppressWarnings("serial")
  protected class AccessibleJPopupMenu extends AccessibleJComponent
      implements PropertyChangeListener {

    /**
     * AccessibleJPopupMenu constructor
     *
     * @since 1.5
     */
    protected AccessibleJPopupMenu() {
      JPopupMenu.this.addPropertyChangeListener(this);
    }

    /**
     * Get the role of this object.
     *
     * @return an instance of AccessibleRole describing the role of the object
     */
    public AccessibleRole getAccessibleRole() {
      return AccessibleRole.POPUP_MENU;
    }

    /**
     * This method gets called when a bound property is changed.
     *
     * @param e A <code>PropertyChangeEvent</code> object describing the event source and the
     * property that has changed. Must not be null.
     * @throws NullPointerException if the parameter is null.
     * @since 1.5
     */
    public void propertyChange(PropertyChangeEvent e) {
      String propertyName = e.getPropertyName();
      if (propertyName == "visible") {
        if (e.getOldValue() == Boolean.FALSE &&
            e.getNewValue() == Boolean.TRUE) {
          handlePopupIsVisibleEvent(true);

        } else if (e.getOldValue() == Boolean.TRUE &&
            e.getNewValue() == Boolean.FALSE) {
          handlePopupIsVisibleEvent(false);
        }
      }
    }

    /*
     * Handles popup "visible" PropertyChangeEvent
     */
    private void handlePopupIsVisibleEvent(boolean visible) {
      if (visible) {
        // notify listeners that the popup became visible
        firePropertyChange(ACCESSIBLE_STATE_PROPERTY,
            null, AccessibleState.VISIBLE);
        // notify listeners that a popup list item is selected
        fireActiveDescendant();
      } else {
        // notify listeners that the popup became hidden
        firePropertyChange(ACCESSIBLE_STATE_PROPERTY,
            AccessibleState.VISIBLE, null);
      }
    }

    /*
     * Fires AccessibleActiveDescendant PropertyChangeEvent to notify listeners
     * on the popup menu invoker that a popup list item has been selected
     */
    private void fireActiveDescendant() {
      if (JPopupMenu.this instanceof BasicComboPopup) {
        // get the popup list
        JList<?> popupList = ((BasicComboPopup) JPopupMenu.this).getList();
        if (popupList == null) {
          return;
        }

        // get the first selected item
        AccessibleContext ac = popupList.getAccessibleContext();
        AccessibleSelection selection = ac.getAccessibleSelection();
        if (selection == null) {
          return;
        }
        Accessible a = selection.getAccessibleSelection(0);
        if (a == null) {
          return;
        }
        AccessibleContext selectedItem = a.getAccessibleContext();

        // fire the event with the popup invoker as the source.
        if (selectedItem != null && invoker != null) {
          AccessibleContext invokerContext = invoker.getAccessibleContext();
          if (invokerContext != null) {
            // Check invokerContext because Component.getAccessibleContext
            // returns null. Classes that extend Component are responsible
            // for returning a non-null AccessibleContext.
            invokerContext.firePropertyChange(
                ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY,
                null, selectedItem);
          }
        }
      }
    }
  } // inner class AccessibleJPopupMenu


  ////////////
// Serialization support.
////////////
  private void writeObject(ObjectOutputStream s) throws IOException {
    Vector<Object> values = new Vector<Object>();

    s.defaultWriteObject();
    // Save the invoker, if its Serializable.
    if (invoker != null && invoker instanceof Serializable) {
      values.addElement("invoker");
      values.addElement(invoker);
    }
    // Save the popup, if its Serializable.
    if (popup != null && popup instanceof Serializable) {
      values.addElement("popup");
      values.addElement(popup);
    }
    s.writeObject(values);

    if (getUIClassID().equals(uiClassID)) {
      byte count = JComponent.getWriteObjCounter(this);
      JComponent.setWriteObjCounter(this, --count);
      if (count == 0 && ui != null) {
        ui.installUI(this);
      }
    }
  }

  // implements javax.swing.MenuElement
  private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    s.defaultReadObject();

    Vector<?> values = (Vector) s.readObject();
    int indexCounter = 0;
    int maxCounter = values.size();

    if (indexCounter < maxCounter && values.elementAt(indexCounter).
        equals("invoker")) {
      invoker = (Component) values.elementAt(++indexCounter);
      indexCounter++;
    }
    if (indexCounter < maxCounter && values.elementAt(indexCounter).
        equals("popup")) {
      popup = (Popup) values.elementAt(++indexCounter);
      indexCounter++;
    }
  }


  /**
   * This method is required to conform to the
   * <code>MenuElement</code> interface, but it not implemented.
   *
   * @see MenuElement#processMouseEvent(MouseEvent, MenuElement[], MenuSelectionManager)
   */
  public void processMouseEvent(MouseEvent event, MenuElement path[],
      MenuSelectionManager manager) {
  }

  /**
   * Processes a key event forwarded from the
   * <code>MenuSelectionManager</code> and changes the menu selection,
   * if necessary, by using <code>MenuSelectionManager</code>'s API.
   * <p>
   * Note: you do not have to forward the event to sub-components.
   * This is done automatically by the <code>MenuSelectionManager</code>.
   *
   * @param e a <code>KeyEvent</code>
   * @param path the <code>MenuElement</code> path array
   * @param manager the <code>MenuSelectionManager</code>
   */
  public void processKeyEvent(KeyEvent e, MenuElement path[],
      MenuSelectionManager manager) {
    MenuKeyEvent mke = new MenuKeyEvent(e.getComponent(), e.getID(),
        e.getWhen(), e.getModifiers(),
        e.getKeyCode(), e.getKeyChar(),
        path, manager);
    processMenuKeyEvent(mke);

    if (mke.isConsumed()) {
      e.consume();
    }
  }

  /**
   * Handles a keystroke in a menu.
   *
   * @param e a <code>MenuKeyEvent</code> object
   * @since 1.5
   */
  private void processMenuKeyEvent(MenuKeyEvent e) {
    switch (e.getID()) {
      case KeyEvent.KEY_PRESSED:
        fireMenuKeyPressed(e);
        break;
      case KeyEvent.KEY_RELEASED:
        fireMenuKeyReleased(e);
        break;
      case KeyEvent.KEY_TYPED:
        fireMenuKeyTyped(e);
        break;
      default:
        break;
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.
   *
   * @param event a <code>MenuKeyEvent</code>
   * @see EventListenerList
   */
  private void fireMenuKeyPressed(MenuKeyEvent event) {
    Object[] listeners = listenerList.getListenerList();
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == MenuKeyListener.class) {
        ((MenuKeyListener) listeners[i + 1]).menuKeyPressed(event);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.
   *
   * @param event a <code>MenuKeyEvent</code>
   * @see EventListenerList
   */
  private void fireMenuKeyReleased(MenuKeyEvent event) {
    Object[] listeners = listenerList.getListenerList();
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == MenuKeyListener.class) {
        ((MenuKeyListener) listeners[i + 1]).menuKeyReleased(event);
      }
    }
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.
   *
   * @param event a <code>MenuKeyEvent</code>
   * @see EventListenerList
   */
  private void fireMenuKeyTyped(MenuKeyEvent event) {
    Object[] listeners = listenerList.getListenerList();
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == MenuKeyListener.class) {
        ((MenuKeyListener) listeners[i + 1]).menuKeyTyped(event);
      }
    }
  }

  /**
   * Messaged when the menubar selection changes to activate or
   * deactivate this menu. This implements the
   * <code>javax.swing.MenuElement</code> interface.
   * Overrides <code>MenuElement.menuSelectionChanged</code>.
   *
   * @param isIncluded true if this menu is active, false if it is not
   * @see MenuElement#menuSelectionChanged(boolean)
   */
  public void menuSelectionChanged(boolean isIncluded) {
    if (DEBUG) {
      System.out.println("In JPopupMenu.menuSelectionChanged " + isIncluded);
    }
    if (invoker instanceof JMenu) {
      JMenu m = (JMenu) invoker;
      if (isIncluded) {
        m.setPopupMenuVisible(true);
      } else {
        m.setPopupMenuVisible(false);
      }
    }
    if (isPopupMenu() && !isIncluded) {
      setVisible(false);
    }
  }

  /**
   * Returns an array of <code>MenuElement</code>s containing the submenu
   * for this menu component.  It will only return items conforming to
   * the <code>JMenuElement</code> interface.
   * If popup menu is <code>null</code> returns
   * an empty array.  This method is required to conform to the
   * <code>MenuElement</code> interface.
   *
   * @return an array of <code>MenuElement</code> objects
   * @see MenuElement#getSubElements
   */
  public MenuElement[] getSubElements() {
    MenuElement result[];
    Vector<MenuElement> tmp = new Vector<MenuElement>();
    int c = getComponentCount();
    int i;
    Component m;

    for (i = 0; i < c; i++) {
      m = getComponent(i);
      if (m instanceof MenuElement) {
        tmp.addElement((MenuElement) m);
      }
    }

    result = new MenuElement[tmp.size()];
    for (i = 0, c = tmp.size(); i < c; i++) {
      result[i] = tmp.elementAt(i);
    }
    return result;
  }

  /**
   * Returns this <code>JPopupMenu</code> component.
   *
   * @return this <code>JPopupMenu</code> object
   * @see MenuElement#getComponent
   */
  public Component getComponent() {
    return this;
  }


  /**
   * A popup menu-specific separator.
   */
  @SuppressWarnings("serial")
  static public class Separator extends JSeparator {

    public Separator() {
      super(JSeparator.HORIZONTAL);
    }

    /**
     * Returns the name of the L&amp;F class that renders this component.
     *
     * @return the string "PopupMenuSeparatorUI"
     * @see JComponent#getUIClassID
     * @see UIDefaults#getUI
     */
    public String getUIClassID() {
      return "PopupMenuSeparatorUI";

    }
  }

  /**
   * Returns true if the <code>MouseEvent</code> is considered a popup trigger
   * by the <code>JPopupMenu</code>'s currently installed UI.
   *
   * @return true if the mouse event is a popup trigger
   * @since 1.3
   */
  public boolean isPopupTrigger(MouseEvent e) {
    return getUI().isPopupTrigger(e);
  }
}
